package com.star.jersey.mybatisplus.base.dal.crud.service.impl;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.google.common.collect.Lists;
import com.star.jersey.mybatisplus.base.core.contract.BaseQueryParam;
import com.star.jersey.mybatisplus.base.core.contract.Page;
import com.star.jersey.mybatisplus.base.dal.crud.service.CrudService;
import com.star.jersey.mybatisplus.base.dal.crud.utils.PageUtils;
import com.star.jersey.mybatisplus.base.utils.Converter;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * curd逻辑层
 *
 * @param <M> 数据库访问实体
 * @param <T> 实体类
 * @param <D> Dto对象
 * @param <Q> 查询对象
 * @author zhouhengzhe
 */
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D, Q extends BaseQueryParam> extends ServiceImpl<M, T> implements CrudService<T, D, Q> {

    private static Pattern humpPattern = Pattern.compile("[A-Z]");

    public CrudServiceImpl() {
    }

    protected Class<D> currentDtoClass() {
        return (Class<D>) ReflectionKit.getSuperClassGenericType(this.getClass(), 2);
    }

    protected <T> Page<T> getPageData(IPage page, Class<T> target) {
        return this.getPageData(page.getRecords(), (int) page.getTotal(), target);
    }

    protected <T> Page<T> getPageData(List<?> list, int total, Class<T> target) {
        List<T> targetList = Converter.copy(list, target);
        return new Page(targetList, total);
    }

    public abstract QueryWrapper<T> getWrapper(Q p);

    @Override
    public Page<D> page(Q queryParam) {
        QueryWrapper<T> wrapper = this.getWrapper(queryParam);
        this.handleWrapper(wrapper, queryParam);
        com.baomidou.mybatisplus.extension.plugins.pagination.Page mpPage = PageUtils.getMPlusPage(queryParam);
        IPage page = this.baseMapper.selectPage(mpPage, wrapper);
        return this.getPageData(page, this.currentDtoClass());
    }

    @Override
    public List<D> list(Q queryParam) {
        if (queryParam == null) {
            return Collections.emptyList();
        } else {
            QueryWrapper<T> wrapper = this.getWrapper(queryParam);
            this.handleWrapper(wrapper, queryParam);
            List<T> entityList = this.baseMapper.selectList(wrapper);
            return Converter.copy(entityList, this.currentDtoClass());
        }
    }

    @Override
    public T selectById(Serializable id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public List<T> selectBatchIds(Collection<? extends Serializable> idList) {
        return this.baseMapper.selectBatchIds(idList);
    }

    @Override
    public D findOne(Q queryParam) {
        if (queryParam == null) {
            return null;
        } else {
            QueryWrapper<T> wrapper = this.getWrapper(queryParam);
            T entityList = this.baseMapper.selectOne(wrapper);
            return Converter.copy(entityList, this.currentDtoClass());
        }
    }

    @Override
    public boolean exist(Q queryParam) {
        List<D> list = this.list(queryParam);
        return list != null && !list.isEmpty();
    }

    @Override
    public D get(Long id) {
        T entity = this.getBaseMapper().selectById(id);
        return Converter.copy(entity, this.currentDtoClass());
    }

    @Override
    public void save(D... dtos) {
        if (dtos == null) {
            return;
        }
        for (D d : dtos) {
            T entity = Converter.copy(d, this.currentModelClass());
            this.save(entity);
            BeanUtils.copyProperties(entity, d);
        }
    }

    @Override
    public void save(List<D> dtos) {
        List<T> entities = Converter.copy(dtos, this.currentModelClass());
        boolean result = this.saveBatch(entities);
        if (result) {
            for (int i = 0; i < entities.size(); ++i) {
                BeanUtils.copyProperties(entities.get(i), dtos.get(i));
            }
        }

    }

    @Override
    public boolean insert(T entity) {
        return SqlHelper.retBool(this.baseMapper.insert(entity));
    }

    @Override
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public boolean insertBatch(Collection<T> entityList) {
        return this.saveBatch(entityList, 100);
    }

    @Override
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public boolean insertBatch(Collection<T> entityList, int batchSize) {
        return this.saveBatch(entityList, batchSize);
    }

    @Override
    public void update(D dto) {
        T entity = Converter.copy(dto, this.currentModelClass());
        this.updateById(entity);
    }

    @Override
    public void update(List<D> dtos) {
        List<T> entities = Converter.copy(dtos, this.currentModelClass());
        this.updateBatchById(entities);
    }

    @Override
    public void delete(Long[] ids) {
        this.baseMapper.deleteBatchIds(Arrays.asList(ids));
    }

    @Override
    public boolean deleteById(Serializable id) {
        return SqlHelper.retBool(this.baseMapper.deleteById(id));
    }

    @Override
    public boolean deleteBatchIds(Collection<? extends Serializable> idList) {
        return SqlHelper.retBool(this.baseMapper.deleteBatchIds(idList));
    }

    private void handleWrapper(QueryWrapper<T> wrapper, BaseQueryParam queryParam) {
        if (StringUtils.isNotBlank(queryParam.getOrderField())) {
            String[] orderFields = this.getOrderFields(queryParam);
            wrapper.orderBy(StringUtils.isNotBlank(queryParam.getOrderField()), queryParam.isAsc(), String.valueOf(Arrays.asList(orderFields)));
        }

    }

    private String[] getOrderFields(BaseQueryParam queryParam) {
        String[] orderFields = queryParam.getOrderField().split(",");
        List<String> orderFiledList = Lists.newArrayList(orderFields);
        Class<T> clazz = this.currentModelClass();
        List<Field> declaredFields = this.getFields(clazz);
        Map<String, Field> fieldMap = (Map) declaredFields.stream().filter((o) -> orderFiledList.contains(o.getName())).collect(Collectors.toMap(Field::getName, (o) -> o, (o1, o2) -> o1));
        String[] dataFields = new String[orderFields.length];

        for (int i = 0; i < orderFiledList.size(); ++i) {
            String orderField = (String) orderFiledList.get(i);
            Field field = (Field) fieldMap.get(orderField);
            if (field != null) {
                TableField annotation = (TableField) field.getAnnotation(TableField.class);
                String fieldName;
                if (annotation != null && StringUtils.isNotBlank(annotation.value())) {
                    fieldName = annotation.value();
                } else {
                    fieldName = this.humpToLine(orderField);
                }

                dataFields[i] = fieldName;
            }
        }

        return dataFields;
    }

    private List<Field> getFields(Class clazz) {
        List<Field> fields = Lists.newArrayList(clazz.getDeclaredFields());
        Class superClazz = clazz.getSuperclass();
        if (superClazz != null && superClazz != Object.class) {
            fields.addAll(this.getFields(superClazz));
        }

        return fields;
    }

    private String humpToLine(String humpString) {
        Matcher matcher = humpPattern.matcher(humpString);
        StringBuffer sb = new StringBuffer();

        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }

        matcher.appendTail(sb);
        return sb.toString();
    }
}